package com.zha.boss.mvc.service.impl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zha.boss.client.RedisOperator;
import com.zha.boss.constants.SysConstants;
import com.zha.boss.dao.entity.PmsMenu;
import com.zha.boss.dao.entity.PmsOperator;
import com.zha.boss.dao.mapper.PmsMenuMapper;
import com.zha.boss.dao.mapper.PmsOperatorMapper;
import com.zha.boss.enums.resp.RespCode;
import com.zha.boss.exceptions.BossException;
import com.zha.boss.mvc.models.dto.LoginResponseDto;
import com.zha.boss.mvc.models.forms.LoginRequestForm;
import com.zha.boss.mvc.models.menu.MenuItem;
import com.zha.boss.mvc.models.menu.SubMenu;
import com.zha.boss.mvc.service.OperatorService;
import com.zha.boss.util.JWTUtil;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

/**
 * @Author: zha
 * @CreateTime: 2024年09月05日 12:52
 */
@Service
public class OperatorServiceImpl implements OperatorService {
    @Resource
    private RedisOperator redisOperator;
    @Resource
    private PmsOperatorMapper pmsOperatorMapper;
    @Resource
    private PmsMenuMapper pmsMenuMapper;
    // 锁对象
    private Object lockObject = new Object();

    @Override
    public LoginResponseDto operatorLogin(LoginRequestForm loginRequestForm) {

        PmsOperator pmsOperator = pmsOperatorMapper.selectOne(new QueryWrapper<PmsOperator>()
                .eq("login_name", loginRequestForm.getLoginName()));
        if (pmsOperator == null){
            // 抛出异常, 自定义异常
            throw new BossException(RespCode.PMS_OPERATOR_NULL);

        }

        // 获取salt值,计算密码,原来的密码计算规则:  (用户提交的密码值(md5)+salt)
        String salt = pmsOperator.getSalt();
        String loginPwd = DigestUtil.md5Hex(loginRequestForm.getLoginPwd() + salt);
        if(!loginPwd.equals(pmsOperator.getLoginPwd())){
            // 抛出异常,登录名称或密码错误
            throw new BossException(RespCode.PMS_LOGIN_FAIL);
        }
        // 分配jwt,使用java的jwt库创建的
        JSONObject jsonObject = new JSONObject();
        jsonObject.set("id", pmsOperator.getId());
        String jwt = JWTUtil.createJWT(jsonObject.toJSONString(0), 120);
        // 存储到redis中 hash类型, 存储的field有operatorId, LoginTime, jwt
        // redis 的 key OPERATOR:TOKEN:operatorId
        String loginTokenKey = SysConstants.OPERATOR_ACCESS_TOKEN + pmsOperator.getId();
        Map<String, String> data = new HashMap<>();
        data.put("operatorId", String.valueOf(pmsOperator.getId()));
        data.put("jwt",jwt);
        data.put("loginTime", DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()));
        Boolean addRedis = redisOperator.setHash(loginTokenKey, data, 120);
        if(addRedis == null || !addRedis){
            // 存储jwt到redis失败,抛出异常
            throw new BossException(RespCode.PMS_JWT_SAVE_REDIS);
        }
        // 登录成功,有jwt
        return new LoginResponseDto(pmsOperator.getId(),pmsOperator.getShowName(),jwt);
    }

    /**
     * 菜单数据的redis缓存
     * 1. 从redis中获取菜单数据,如果有数据，那就直接返回
     * 2. 如果redis没有数据，从数据库读取菜单数据，整理数据，存储到redis中
     * 3. 当从redis中获取菜单数据有异常时,从数据库尝试获取
     * 数据存储格式,使用 json, array
     * redis使用的数据类型 string
     * @param operatorId
     * @return
     */
    @Override
    public List<SubMenu> operatorMenuList(Long operatorId) {
        List<SubMenu> subMenuList = new ArrayList<>();
        // 1. 从redis中获取菜单数据,如果有数据,直接返回
        String menuRedisKey = SysConstants.OPERATOR_MENUS + operatorId;
        try {
            String menuJsonData = redisOperator.getValue(menuRedisKey);
            if (StrUtil.isNotBlank(menuJsonData)){
                // 将json转为list<SubMenu>
                subMenuList = JSONUtil.toList(menuJsonData, SubMenu.class);
            }else {
                // 2. 如果redis没有数据,从数据库读取菜单数据
                synchronized (lockObject ){
                    menuJsonData = redisOperator.getValue(menuRedisKey);
                    if (StrUtil.isNotBlank(menuJsonData)){
                        // 将json转为list<SubMenu>
                        subMenuList = JSONUtil.toList(menuJsonData, SubMenu.class);
                    }else {
                        subMenuList = getOperatorMenusFromDb(operatorId);
                        // 3. 将读取到的数据存储到redis
                        redisOperator.setValue(menuRedisKey, JSONUtil.toJsonStr(subMenuList));
                    }
                }

            }
        } catch (Exception e) {
            subMenuList = getOperatorMenusFromDb(operatorId);
            e.printStackTrace();
        }
        return subMenuList;
    }

    @Override
    public void operatorLogout(Long operatorId) {
        String tokenKey = SysConstants.OPERATOR_ACCESS_TOKEN + operatorId;
        Boolean deleted = redisOperator.delete(tokenKey);
        if (!deleted){
            throw new BossException(RespCode.UNKNOWN);
        }
    }

    private List<SubMenu> getOperatorMenusFromDb(Long operatorId) {
        // 1. 从数据库查询菜单的数据
        List<PmsMenu> pmsMenuList = pmsMenuMapper.selectMenusByOperatorId(operatorId);
        // 2. 整理数据List-Map<String, List<PmsMenu>>
        String number = "";
        Map<String, List<PmsMenu>> menuMap = new HashMap<>();
        for (PmsMenu pmsMenu : pmsMenuList) {
            number = pmsMenu.getNumber().substring(0, 4);
            if (menuMap.containsKey(number)) {
                menuMap.get(number).add(pmsMenu);
            }else {  // 没有number这个key
                List<PmsMenu> menus = new ArrayList<>();
                menus.add(pmsMenu);
                // 添加新的菜单
                menuMap.put(number, menus);
            }

        }
        // 将Map<String, List<PmsMenu>> 数据转换为List<SubMenu>
        List<SubMenu> subMenuList = new ArrayList<>();
        menuMap.forEach((key,menus) -> {
            SubMenu subMenu = new SubMenu();
            List<MenuItem> itemList = new ArrayList<>();
            for (PmsMenu menu : menus) {
                if ("NO".equals(menu.getIsLeaf())){
                    subMenu.setId(menu.getId());
                    subMenu.setName(menu.getName());
                    subMenu.setLeaf(menu.getIsLeaf());
                    subMenu.setUrl(menu.getUrl());
                    subMenu.setNumber(menu.getNumber());
                }else {
                    MenuItem item = new MenuItem();
                    item.setId(menu.getId());
                    item.setName(menu.getName());
                    item.setLeaf(menu.getIsLeaf());
                    item.setUrl(menu.getUrl());
                    item.setNumber(menu.getNumber());
                    itemList.add(item);
                }
            }
            subMenu.setItems(itemList);
            subMenuList.add(subMenu);
        });
        System.out.println("subMenuList = " + subMenuList);
        return subMenuList.stream().sorted(Comparator.comparing(SubMenu::getNumber)).toList();
    }
}
